{T}

GitHub Actions 与 GitLab CI 对比

一、模块介绍

GitHub ActionsGitLab CI/CD 是当前最主流的两个"代码托管平台原生"CI/CD 系统。相比传统 Jenkins,它们的核心优势是与代码仓库深度集成——无需额外维护 CI 服务器,推送代码即触发流水线。

GitHub Actions 于 2019 年发布,依托 GitHub 的庞大生态与 Marketplace 迅速普及;GitLab CI 是 GitLab 平台的核心内置能力,以一体化的 DevOps 平台定位著称。两者在设计理念、语法模型、执行架构上各有侧重。本文系统对比两者的核心能力、语法差异、执行模型、以及选型建议。

二、核心方法论

2.1 架构对比

图表渲染中…

2.2 核心概念对照表

概念GitHub ActionsGitLab CI/CD
流水线定义文件.github/workflows/*.yml.gitlab-ci.yml
流水线WorkflowPipeline
阶段JobStage(包含多个 Job)
步骤Step(在 Job 内)Script(在 Job 的 script 中)
执行器RunnerRunner
并行矩阵strategy.matrixparallel:matrix
复用单元Reusable Workflow / Composite Actioninclude: / template:
触发条件on:rules: / only/except
缓存actions/cachecache:
密钥管理Repository SecretsCI/CD Variables
环境保护Environment Protection RulesProtected Environments

2.3 执行模型差异

图表渲染中…

关键差异:GitHub Actions 的 Job 之间默认无依赖可并行,也可通过 needs 显式定义依赖;GitLab CI 的 Stage 之间默认串行,同一 Stage 内的 Job 并行执行。

三、关键流程

3.1 GitHub Actions 完整示例

yaml
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:  # 手动触发
    inputs:
      environment:
        description: '部署环境'
        type: choice
        options: [test, staging, production]
 
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
 
jobs:
  # ===== 第一层:构建与单元测试 =====
  build-test:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    outputs:
      version: ${{ steps.meta.outputs.version }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - uses: actions/setup-python@v5
        with:
          python-version: '3.13'
          cache: 'pip'
 
      - name: 安装依赖
        run: pip install -r requirements.txt
 
      - name: 静态检查
        run: |
          pip install ruff
          ruff check src/
 
      - name: 单元测试 + 覆盖率
        run: |
          pytest tests/unit/ --cov=src --cov-fail-under=80 \
            --cov-report=xml --junitxml=test-results.xml
 
      - name: 上传测试结果
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-results
          path: test-results.xml
 
      - name: 上传覆盖率到 Codecov
        uses: codecov/codecov-action@v4
        with:
          file: coverage.xml
          token: ${{ secrets.CODECOV_TOKEN }}
 
      - name: 生成版本号
        id: meta
        run: |
          if [ "${{ github.ref }}" = "refs/heads/main" ]; then
            echo "version=$(date +%Y%m%d)-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
          else
            echo "version=pr-${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
          fi
 
  # ===== 第二层:集成测试(矩阵并行) =====
  integration-test:
    needs: build-test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.11', '3.12', '3.13']
        database: ['postgres:16', 'mysql:8.4']
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
 
      - name: 启动数据库容器
        run: docker run -d --name db -e POSTGRES_PASSWORD=test ${{ matrix.database }}
 
      - name: 集成测试
        run: pytest tests/integration/ -m integration
 
  # ===== 第三层:构建镜像并推送 =====
  docker-build:
    needs: build-test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
 
      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - uses: docker/metadata-action@v5
        id: meta
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=raw,value=${{ needs.build-test.outputs.version }}
            type=raw,value=latest
 
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
 
  # ===== 第四层:部署(环境保护) =====
  deploy:
    needs: docker-build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: ${{ github.event.inputs.environment || 'staging' }}
      url: https://app.staging.example.com
    steps:
      - name: 部署到 K8s
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig
          export KUBECONFIG=kubeconfig
          kubectl set image deployment/app \
            app=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.build-test.outputs.version }}
          kubectl rollout status deployment/app --timeout=5m

3.2 GitLab CI 完整示例

yaml
# .gitlab-ci.yml
stages:
  - build
  - test
  - quality
  - deploy
 
variables:
  REGISTRY: registry.gitlab.com
  IMAGE_NAME: $CI_REGISTRY_IMAGE
  PYTHON_VERSION: "3.13"
 
# 默认配置
default:
  image: python:${PYTHON_VERSION}-slim
  cache:
    paths:
      - .pip-cache/
  before_script:
    - pip install --cache-dir .pip-cache -r requirements.txt
 
# ===== Build Stage =====
build:
  stage: build
  script:
    - python -m build
    - pip install dist/*.whl
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour
 
# ===== Test Stage(并行) =====
unit-test:
  stage: test
  script:
    - pip install pytest pytest-cov ruff
    - ruff check src/
    - pytest tests/unit/ --cov=src --cov-fail-under=80 --junitxml=report.xml
  coverage: '/TOTAL.*\s+(\d+\%)$/'
  artifacts:
    reports:
      junit: report.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml
 
integration-test:
  stage: test
  services:
    - postgres:16
    - redis:8-alpine
  variables:
    POSTGRES_PASSWORD: test
    DATABASE_URL: postgresql://postgres:test@postgres/test
  script:
    - pytest tests/integration/ -m integration
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
 
# ===== Quality Stage =====
sonarqube:
  stage: quality
  image: sonarsource/sonar-scanner-cli
  script:
    - sonar-scanner
      -Dsonar.projectKey=$CI_PROJECT_NAME
      -Dsonar.host.url=$SONAR_HOST
      -Dsonar.login=$SONAR_TOKEN
  allow_failure: false  # 质量门禁必须通过
 
# ===== Deploy Stage =====
deploy:staging:
  stage: deploy
  image: bitnami/kubectl:latest
  script:
    - kubectl set image deployment/app app=$IMAGE_NAME:$CI_COMMIT_SHORT_SHA -n staging
    - kubectl rollout status deployment/app -n staging --timeout=5m
  environment:
    name: staging
    url: https://app.staging.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  when: manual  # 手动确认部署
 
deploy:production:
  stage: deploy
  extends: deploy:staging  # 继承配置
  environment:
    name: production
    url: https://app.example.com
  rules:
    - if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG
  when: manual
  needs:
    - deploy:staging  # 生产部署依赖预发布成功

3.3 复用机制对比

复用方式GitHub ActionsGitLab CI
跨仓库复用Reusable Workflow (workflow_call)include: 远程模板
同仓库复用Composite Actionextends: / !reference [...]
社区生态Marketplace(数千个 Action)GitLab CI Templates
参数传递inputs / secretsvariables / include:template

四、工具与实践

4.1 缓存策略对比

yaml
# GitHub Actions 缓存
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      node_modules
    key: ${{ runner.os }}-deps-${{ hashFiles('**/requirements*.txt', '**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-deps-
yaml
# GitLab CI 缓存
cache:
  key:
    files:
      - requirements.txt
      - package-lock.json
  paths:
    - .pip-cache/
    - node_modules/
  policy: pull-push  # 拉取并推送更新

4.2 矩阵测试对比

yaml
# GitHub Actions 矩阵
strategy:
  fail-fast: false  # 一个失败不取消其他
  matrix:
    os: [ubuntu-latest, macos-latest, windows-latest]
    python: ['3.11', '3.12', '3.13']
    exclude:
      - os: windows-latest
        python: '3.11'  # 排除特定组合
    include:
      - os: ubuntu-latest
        python: '3.14'  # 额外增加组合
yaml
# GitLab CI 矩阵(13.x+ 支持)
test:
  parallel:
    matrix:
      - OS: [ubuntu, macos, windows]
        PYTHON: ['3.11', '3.12', '3.13']
  script:
    - pytest tests/ --os=${OS} --python=${PYTHON}

4.3 安全扫描能力

安全能力GitHub ActionsGitLab CI
SAST(静态安全分析)CodeQL(原生)GitLab SAST(内置)
依赖扫描Dependabot(原生)Dependency Scanning(内置)
容器扫描Trivy ActionContainer Scanning(内置)
密钥检测Secret Scanning(原生)Secret Detection(内置)
DAST(动态安全分析)第三方 ActionGitLab DAST(内置)

GitLab 的安全扫描能力开箱即用程度更高,GitHub 的 CodeQL 代码分析深度更强

五、常见误区

5.1 "GitHub Actions 只能用 GitHub 仓库"

误区:认为使用 GitHub Actions 必须将代码托管在 GitHub。

纠正:GitHub 支持通过 repository_dispatchworkflow_dispatch 接收外部 Webhook 触发。但最佳实践仍是代码与 CI 同平台。

5.2 忽视 Runner 安全

误区:在自托管 Runner 上以 root 执行流水线,且多个项目共享 Runner。

纠正:自托管 Runner 有安全风险——流水线脚本可访问 Runner 主机。应按项目隔离 Runner,使用容器化执行,限制权限。

5.3 YAML 过度膨胀

误区:单一 workflow 文件超过 500 行,难以维护。

纠正:拆分为多个 workflow(按触发条件或阶段),使用 Composite Action / include template 复用公共逻辑。

5.4 忽视执行成本

误区:每次 PR 触发完整流水线(含 30 分钟的 E2E),GitHub Actions 免费额度耗尽。

纠正:利用 paths 过滤器只在相关文件变更时触发;PR 级只跑快速测试,合并到 main 后跑完整测试。

六、进阶扩展与参考

6.1 选型决策矩阵

场景推荐理由
开源项目GitHub Actions免费额度充足、社区生态强
私有企业一体化GitLab CI代码+CI+CD+安全一体化
K8s 原生团队GitLab CI + K8s RunnerRunner 与 K8s 集成更成熟
多仓库企业GitLab CI + include跨仓库模板复用更灵活
GitHub 生态深度用户GitHub Actions与 PR/Issue/Dependabot 无缝集成

6.2 与 ArgoCD/Tekton 的协同

两者都可与 GitOps 工具协同:CI 平台负责构建与测试,测试通过后更新 Git 仓库中的部署清单,ArgoCD 监听变更自动部署。Tekton 则可作为 K8s 原生 CI 引擎替代传统 CI Runner。

6.3 推荐参考

  • 文档:GitHub Actions(docs.github.com/actions)
  • 文档:GitLab CI/CD(docs.gitlab.com/ee/ci)
  • 对比:ThoughtWorks Technology Radar 对 CI 平台的评估
  • 实践:GitHub Actions Reusable Workflows 最佳实践